#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define G_LOG_DOMAIN (component_name)
#include <kewinternal.h>

#define STDIN_FD 0
#define STDOUT_FD 1

#define SOURCE_FILE_NAME "'kew' container"

static GMainLoop *main_loop;
static int exit_code;
static char *log_domain;

static char *usage_name;
static char *component_name;
static char *system_component_dir;
static gboolean use_system_component;

static KewObject *component_object;
static KewObject *arguments_object;
static KewObject *log_object;
static KewObject *container_object;
static KewObject *input_object;
static KewObject *output_object;
static KewObject *files_object;

static gboolean load_component_callback(char *component_name);



/* Container */
METHOD(Container_Shutdown)
{
  exit_code = 0;
  g_main_quit(main_loop);
  results[0] = KewNil;
}

METHOD(Container_Abort)
{
  KewPrintStackTrace();
  exit_code = 1;
  g_error("'Container abort' executed");
  results[0] = KewNil; /* Not reached */
}

METHOD(Container_Log)
{
  results[0] = log_object;
}

METHOD(Container_Version)
{
  results[0] = KewNewString(g_strconcat(KewVersion(), " (standalone)", NULL));
}

METHOD(Container_Exception)
{
  /* FIXME: should check specially for string type arguments and not
     allow another exception to be thrown */
  g_error("Unhandled exception: '%s'", KewStringVal(args[0]));
  results[0] = KewNil; /* Not reached */
}

static gboolean
Container_Repeat_Callback(KewObject *block)
{
  return KewIsTrue(KewSend0(block, "do"));
}

METHOD(Container_OnInterval_2_1)
{
  /* FIXME: validate types */
  g_timeout_add(KewIntVal(args[0]),
                (GSourceFunc) Container_Repeat_Callback,
                (gpointer) args[1]);
  results[0] = KewNil;
}

METHOD(Container_OnInterval_2_0)
{
  /* FIXME: validate types */
  g_timeout_add(KewIntVal(args[0]),
                (GSourceFunc) Container_Repeat_Callback,
                (gpointer) args[1]);
}

METHOD(Container_OnIdle_1_1)
{
  /* FIXME: validate types */
  g_idle_add((GSourceFunc) Container_Repeat_Callback,
             (gpointer) args[0]);
  results[0] = KewNil;
}

METHOD(Container_OnIdle_1_0)
{
  /* FIXME: validate types */
  g_idle_add((GSourceFunc) Container_Repeat_Callback,
             (gpointer) args[0]);
  results[0] = KewNil;
}

METHOD(Container_SystemComponent)
{
  KewLoadComponent(system_component_dir, KewStringVal(args[0]), args+1, 1, results, 1);
}

METHOD(Container_ApplicationComponent)
{
  /* FIXME: just using the CWD is pretty limited */
  KewLoadComponent(NULL, KewStringVal(args[0]), args+1, 1, results, 1);
}

METHOD(Container_Input)
{
  results[0] = input_object;
}

METHOD(Container_Output)
{
  results[0] = output_object;
}

METHOD(Container_Files)
{
  results[0] = files_object;
}

METHOD(Container_Parameters)
{
  results[0] = arguments_object;
}

static KewObjectType container_type = {
  SOURCE_FILE_NAME, __LINE__,
  0,
  {
    {"=", Kew__GenericEqual, 1, 1},
    {"hash", Kew__GenericHash, 0, 1},
    {"log", Container_Log, 0, 1},
    {"input", Container_Input, 0, 1},
    {"output", Container_Output, 0, 1},
    {"files", Container_Files, 0, 1},
    {"shutdown", Container_Shutdown, 0, 0},
    {"shutdown", Container_Shutdown, 0, 1}, /* Deprecated */
    {"abort", Container_Abort, 0, 0},
    {"abort", Container_Abort, 0, 1}, /* Deprecated */
    {"exception", Container_Exception, 1, 1},
    {"onInterval", Container_OnInterval_2_0, 2, 0},
    {"onIdle", Container_OnIdle_1_0, 1, 0},
    {"onInterval", Container_OnInterval_2_1, 2, 1}, /* Deprecated */
    {"onIdle", Container_OnIdle_1_1, 1, 1}, /* Deprecated */
    {"systemComponent", Container_SystemComponent, 2, 1},
    {"applicationComponent", Container_ApplicationComponent, 2, 1},
    {"systemModule", Container_SystemComponent, 2, 1}, /* Deprecated */
    {"applicationModule", Container_ApplicationComponent, 2, 1}, /* Deprecated */
    {"parameters", Container_Parameters, 0, 1},
    {"version", Container_Version, 0, 1},
    {NULL, NULL, 0, 0}
  }
};



/* Log */

METHOD(Container_Log_Message)
{
  g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "%s", KewStringVal(args[0]));
  results[0] = KewNil;
}

METHOD(Container_Log_Error)
{
  g_log(G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "%s", KewStringVal(args[0]));
  results[0] = KewNil;
}

METHOD(Container_Log_Warning)
{
  g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "%s", KewStringVal(args[0]));
  results[0] = KewNil;
}

static KewObjectType log_type = {
  SOURCE_FILE_NAME, __LINE__,
  0,
  {
    {"=", Kew__GenericEqual, 1, 1},
    {"hash", Kew__GenericHash, 0, 1},
    {"message", Container_Log_Message, 1, 0},
    {"warning", Container_Log_Warning, 1, 0},
    {"error", Container_Log_Error, 1, 0},
    {NULL, NULL, 0, 0}
  }
};


/* Files */
static KewObject *
do_file_open(char *filename, int flags, int permissions)
{
  int fd = open(filename, flags, permissions);
  if (fd >= 0)
    return KewNewUnixIOChannel(fd);
  else {
    g_error("opening file '%s' failed: %s", filename, g_strerror(errno));
    return KewNil; /* Not reached */
  }
}

METHOD(Container_Files_OpenRead)
{
  results[0] = do_file_open(KewStringVal(args[0]), O_RDONLY, 0);
}

METHOD(Container_Files_OpenWrite)
{
  results[0] = do_file_open(KewStringVal(args[0]), O_WRONLY | O_CREAT | O_TRUNC, 0600);
}

METHOD(Container_Files_OpenReadWrite)
{
  results[0] = do_file_open(KewStringVal(args[0]), O_RDWR | O_CREAT, 0600);
}

static KewObjectType files_type = {
  SOURCE_FILE_NAME, __LINE__,
  0,
  {
    {"=", Kew__GenericEqual, 1, 1},
    {"hash", Kew__GenericHash, 0, 1},
    {"openRead", Container_Files_OpenRead, 1, 1},
    {"openWrite", Container_Files_OpenWrite, 1, 1},
    {"openReadWrite", Container_Files_OpenReadWrite, 1, 1},
    {NULL, NULL, 0, 0}
  }
};



/* Startup */

static gboolean
load_component_callback(char *component_name)
{
  KewObject *args[1];
  KewObject *results[1];

  char *component_dir = use_system_component ? system_component_dir : ".";
  args[0] = container_object;
  KewLoadComponent(component_dir, component_name, args, 1, results, 1);
  component_object = results[0];
  return FALSE;
}


static void
show_usage()
{
    printf("Usage: %s [-s] [-d <STANDARD_DIR>] <COMPONENT_NAME> [<COMPONENT_ARGS>]\n"
           "   or: %s [-V]\n"
           "\n"
           "-d DIR     override location of standard components\n"
           "-s         load a standard component instead of an application component\n"
           "--version  print version and copyright information and exit\n"
           "--help     print this message and exit\n"
           "\n"
           "<COMPONENT_ARGS> are made available to the component by the container.\n",
           usage_name, usage_name);
}


int
main(int argc, char *argv[])
{
  unsigned current_arg = 1;

  usage_name = argc > 0 ? argv[0] : "(kew)";
  system_component_dir = KEW_SYSTEM_COMPONENT_DIR;
  use_system_component = FALSE;

  /* Scan for container options */
  while (current_arg < argc) {
    if (strcmp(argv[current_arg], "-d") == 0) {
      current_arg++;
      if (current_arg >= argc) {
        show_usage();
        exit(1);
      }
      system_component_dir = argv[current_arg];
    } else if (strcmp(argv[current_arg], "-s") == 0) {
      use_system_component = TRUE;
    } else if (strcmp(argv[current_arg], "--version") == 0) {
      KewInit();
      printf("kew-%s %s\n", KewVersion(), KewCopyright());
      exit(0);
    } else if (strcmp(argv[current_arg], "--help") == 0) {
      show_usage();
      exit(0);
    } else break;
    current_arg++;
  }

  /* Look for component name */
  if (current_arg < argc) {
    component_name = argv[current_arg];
    current_arg++;
  } else {
    show_usage();
    exit(1);
  }

  /* Initialize Kew and prepare container */
  KewInit();
  arguments_object = KewWrapStringArray(argv+current_arg, argc-current_arg);
  log_object = KewNewObject(&log_type);
  files_object = KewNewObject(&files_type);
  container_object = KewNewObject(&container_type);
  input_object = KewNewUnixIOChannel(STDIN_FD);
  output_object = KewNewUnixIOChannel(STDOUT_FD);

  /* Hook next stage and enter main loop */
  exit_code = 0;
  main_loop = g_main_new(FALSE);
  g_idle_add((GSourceFunc) load_component_callback,
             (gpointer) component_name);
  g_main_run(main_loop);

  /* Shutdown */
  g_main_destroy(main_loop);
  return exit_code;
}
